package com.chenfan.ccp.plug.mybatis.handler;

import com.chenfan.ccp.plug.mybatis.annotation.Column;
import com.chenfan.ccp.plug.mybatis.annotation.Table;
import com.chenfan.ccp.plug.mybatis.config.MybatisConfig;
import com.chenfan.ccp.plug.mybatis.annotation.Id;

import java.io.*;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * 生成实体类
 */
public class GenerateEntity {

	private static final char UNDERLINE = '_';
	private static final String TABLES_PREFIX_SIGN = ",";
	private static final String BLUR_TABLE_NAME = "*";
	private static final String EMPTY = "";
	private static final String columnReg = "\\((.*)\\)";
	private static final Pattern regionPattern = Pattern.compile(columnReg);


	/**
	 * 将"_"转成驼峰
	 */
	private static String underlineToCamel(String param, String prefix) {
		if (param == null || param.isEmpty()) {
			return "";
		}
		if (prefix != null && !prefix.isEmpty()) {

			String regex = "^(" + prefix.replaceAll(TABLES_PREFIX_SIGN, "|") + ")";

			boolean matches = Pattern.matches(regex + "\\w+", param);
			if (matches) {
				param = param.replaceAll(regex, EMPTY);
			}
		}


		int len = param.length();
		StringBuilder sb = new StringBuilder(len);
		for (int i = 0; i < len; i++) {
			char c = param.charAt(i);
			if (i == 0 && c != UNDERLINE && Character.isLowerCase(c)) {
				c -= 32;
			}
			if (c == UNDERLINE) {
				if (++i < len) {
					sb.append(Character.toUpperCase(param.charAt(i)));
				}
			} else {
				sb.append(c);
			}
		}
		return sb.toString();
	}


	/**
	 * 生成表对应的实体类
	 *
	 * @param isModify              是否修正错误表的错误字段
	 * @param tables                需要生成的表名 可以利用*表示模糊 也可以不填写 默认所有
	 * @param prefix                需要去除表的前缀 多个利用,分开,没有可以不填写
	 * @param existPackageNameBase  已经存在的实体类的路径 多个可以用,分开
	 * @param toPackageNameBase     需要生成类的包名,建议把之前的实体类都移同一个地方管理
	 * @param conn                  数据库连接信息
	 * @param currentSourceFileName 当前源文件的名称
	 * @throws SQLException
	 * @throws IOException
	 * @throws ClassNotFoundException
	 * @throws URISyntaxException
	 */
	public static void generateJavaFile(Boolean isModify, String[] tables, String prefix, String existPackageNameBase,
										String toPackageNameBase, Connection conn, String currentSourceFileName) throws SQLException, IOException, ClassNotFoundException, URISyntaxException {
		Map<String, String> queryTableNames = new HashMap<>(tables.length);
		Set<String> blurTableName = new HashSet<>();
		for (String name : tables) {
			if (name.isEmpty()) {
				continue;
			}

			if (name.contains(BLUR_TABLE_NAME)) {
				blurTableName.add(name);
			} else {
				queryTableNames.put(name, name);
			}
		}


		//获取所有的表名
		PreparedStatement ps = null;

		ResultSet resultSet = null;
		try {
			ps = conn.prepareStatement("show tables");

			resultSet = ps.executeQuery();

			Map<String, String> tablesNames = new HashMap<>();
			String name;
			while (resultSet.next()) {

				name = resultSet.getString(1);

				//当所填的表为空 直接添加所有
				if (queryTableNames.isEmpty() && blurTableName.isEmpty()) {
					tablesNames.put(name, name);
					continue;
				}

				//特定的表
				if (queryTableNames.containsKey(name)) {
					tablesNames.put(name, name);
				} else {

					//模糊查询表名
					if (!blurTableName.isEmpty()) {
						for (String blurName : blurTableName) {
							if (Pattern.matches(blurName.replaceAll("\\" + BLUR_TABLE_NAME,
								"(\\\\w+|)"), name)) {
								tablesNames.put(name, name);
							}
						}
					}
				}
			}


			if (tablesNames.isEmpty()) {
				System.err.println("tables is empty");
				return;
			}

			//查询已经存在的实体类的名称
			Set<Class<?>> hasExistTablesClasss = new HashSet<>();
			DbHandler.getAllClass(existPackageNameBase, hasExistTablesClasss);

			Map<String, String> needCreateTableNames = new HashMap<>();
			for (Class aclass : hasExistTablesClasss) {
				String tablesName = getTableNameByClass(aclass);
				if (tablesNames.containsKey(tablesName)) {
					tablesNames.put(tablesName, EMPTY);
				}
			}

			//找出需要生成的实体类名称
			for (Map.Entry<String, String> entry : tablesNames.entrySet()) {
				if (entry.getValue().isEmpty()) {
					continue;
				}
				needCreateTableNames.put(entry.getValue(), underlineToCamel(entry.getKey(), prefix));
			}


			//生成实体类的文件
			writeFile(isModify, conn, needCreateTableNames, toPackageNameBase, currentSourceFileName);

		} finally {
			try {
				conn.close();
			} catch (SQLException ex) {

			}
			if (resultSet != null) {
				try {
					resultSet.close();
				} catch (SQLException ex) {

				}

			}
			if (ps != null) {
				try {
					ps.close();
				} catch (SQLException ex) {

				}

			}


		}

	}


	private static void writeFile(Boolean isModify, Connection connection, Map<String, String> tablesNames, String pageNameBase, String currentSourceFileName) throws SQLException, URISyntaxException, IOException {
		PreparedStatement ps;
		ResultSet rs;
		StringBuilder packageBuilder = new StringBuilder();

		packageBuilder.append("package ");
		packageBuilder.append(pageNameBase);
		packageBuilder.append(";\n");

		String changeColumn = "alter table `%s` change column `%s`  `%s`  %s %s %s";

		//获取数据库名称
		rs = connection.prepareStatement("SELECT DATABASE()").executeQuery();
		rs.next();
		String db = rs.getString(1);


		//查看表结构
		for (Map.Entry<String, String> entry : tablesNames.entrySet()) {
			ps = connection.prepareStatement(String.format("desc %s", entry.getKey()));
			rs = ps.executeQuery();

			//导包
			StringBuilder importBuilder = new StringBuilder();

			importBuilder.append("import ");
			importBuilder.append(Table.class.getCanonicalName());
			importBuilder.append(";\n");

			importBuilder.append("import ");
			importBuilder.append(Id.class.getCanonicalName());
			importBuilder.append(";\n");
			StringBuilder fieldBuilder = new StringBuilder();

			Map<String, String> columnCommit = getColumnCommit(entry.getKey(), db, connection);

			while (rs.next()) {


				//实体映射
				String fieldName = rs.getString(1);

				String fieldType = rs.getString(2);
				String defaultVal = rs.getString(5);
				String isNotNull = rs.getString(3);

				String javaTypeName = SqlRelationJavaUtil.RELATION_MAP.get(fieldType.toLowerCase().replaceAll(columnReg, ""));

				if (javaTypeName != null) {

					String importType = SqlRelationJavaUtil.JAVA_TYPE_MAP.get(javaTypeName);
					if (!importType.isEmpty() && !importBuilder.toString().contains(importType)) {
						importBuilder.append("import ");
						importBuilder.append(importType);
						importBuilder.append(";\n");
					}


					//进行自定义的column注释的书写
					String commit = columnCommit.get(fieldName);
					//组建属性
					String column = lowerFirstCase(underlineToCamel(fieldName, ""));

					String newColumnName = camel2Underline(column);
					Boolean isPrimary = rs.getString(4).equalsIgnoreCase("pri");

					if (isPrimary) {
						fieldBuilder.append("@Id\n");
					} else {
						if (!commit.isEmpty()) {

							fieldBuilder.append("@Column(value = \"");
							fieldBuilder.append(isModify ? newColumnName : fieldName);
							fieldBuilder.append("\",commit = \"");
							fieldBuilder.append(commit);
							fieldBuilder.append("\"");

							Matcher regionMatcher = regionPattern.matcher(fieldType.toLowerCase());

							if (regionMatcher.find()) {

								String lengths = regionMatcher.group(1);

								if (lengths.contains(",")) {
									String[] lengthArr = lengths.split(",");
									fieldBuilder.append(",length =");
									fieldBuilder.append(Long.valueOf(lengthArr[0]));
									fieldBuilder.append(",precision =");
									fieldBuilder.append(Long.valueOf(lengthArr[1]));
								} else {

									fieldBuilder.append(",length =");
									fieldBuilder.append(Long.valueOf(lengths));
								}


							}

							if (isNotNull != null && "NO".equalsIgnoreCase(isNotNull)) {

								fieldBuilder.append(",isNUll = false");
							}

							if (defaultVal != null) {

								//默认的时间分配
								if ("CURRENT_TIMESTAMP".equalsIgnoreCase(defaultVal)) {

									if (!importBuilder.toString().contains(DbHandler.class.getCanonicalName())) {
										importBuilder.append("import ");
										importBuilder.append(DbHandler.class.getCanonicalName());
										importBuilder.append(";\n");
									}

									fieldBuilder.append(", dateGeneStrategy =  DbHandler.DateGeneStrategy.CREATE");

								} else {

									fieldBuilder.append(", defaultVal = \"");
									fieldBuilder.append(defaultVal);
									fieldBuilder.append("\"");
								}


							}


							fieldBuilder.append(")\n");


							//增加import
							if (!importBuilder.toString().contains(Column.class.getCanonicalName())) {
								importBuilder.append("import ");
								importBuilder.append(Column.class.getCanonicalName());
								importBuilder.append(";\n");

							}
						}

					}

					//进行数据的更正
					if (!fieldName.equals(newColumnName) && isModify) {

						connection.prepareStatement(String.format(changeColumn,
							entry.getKey(), fieldName, newColumnName, fieldType, defaultVal == null ? EMPTY :
								"DEFAULT " + (defaultVal.isEmpty() ? "''" : defaultVal),
							commit.isEmpty() ? EMPTY :
								((isPrimary ? "NOT NULL AUTO_INCREMENT " : EMPTY) + " COMMENT '" + commit + "'"))).execute();
						System.err.printf("table %s oldColumnName %s rename %s success%n", entry.getKey(), fieldName, newColumnName);
					}


					fieldBuilder.append("private ");
					fieldBuilder.append(javaTypeName);
					fieldBuilder.append(" ");
					fieldBuilder.append(column);
					fieldBuilder.append(";\n");

				}
			}

			//拼接
			if (fieldBuilder.length() != 0) {
				StringBuilder builder = new StringBuilder();
				builder.append(packageBuilder.toString());
				builder.append(importBuilder.toString());
				builder.append("import lombok.Data;\n @Data\n ");


				String tableCommit = getTableCommit(entry.getKey(), db, connection);
				if (tableCommit != null && !tableCommit.isEmpty()) {
					builder.append("@Table(value=\"");
					builder.append(entry.getKey());
					builder.append("\"");
					builder.append(",commit=\"");
					builder.append(tableCommit);
					builder.append("\"");

				} else {
					builder.append("@Table(\"");
					builder.append(entry.getKey());
					builder.append("\"");

				}


				builder.append(")\n public class ");
				builder.append(entry.getValue());
				builder.append(" {\n");
				builder.append(fieldBuilder.toString());
				builder.append("}");

				String currentSourcePath = getCurrentSourcePath(currentSourceFileName);
				if (currentSourcePath != null && !currentSourcePath.isEmpty()) {


					BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(
						new File(currentSourcePath + File.separator + pageNameBase.replaceAll("\\.",
							File.separator.equalsIgnoreCase("\\") ? "\\\\" : File.separator)
							, entry.getValue() + ".java")));


					bufferedOutputStream.write(builder.toString().getBytes(StandardCharsets.UTF_8));
					bufferedOutputStream.flush();
					bufferedOutputStream.close();

				} else {
					System.err.println(pageNameBase + "cannot find sourcePath !!");
				}
			}

		}


	}

	/**
	 * 获取当前源文件路径
	 *
	 * @param currentSourceFileName 当前源文件的名称
	 * @return
	 */
	private static String getCurrentSourcePath(String currentSourceFileName) {
		String absoluteFiles = getAbsoluteFiles(currentSourceFileName,
			Paths.get(".").toFile());


		return absoluteFiles.substring(0, absoluteFiles.lastIndexOf(currentSourceFileName));
	}


	/**
	 * 根据路径获取对应的文件的绝对路径
	 *
	 * @param fileName 文件的地址
	 * @param pathFile 文件的根目录
	 * @return
	 */
	private static String getAbsoluteFiles(String fileName, File pathFile) {
		File[] files = pathFile.listFiles();
		String resultPath = "";
		for (File file : files) {
			if (!resultPath.isEmpty()) {
				break;
			}
			if (file.isDirectory()) {
				resultPath = getAbsoluteFiles(fileName, file);
			} else if (file.getName().endsWith(".java") && file.getAbsolutePath().endsWith(fileName)) {
				resultPath = file.getAbsolutePath();
				break;
			}
		}

		return resultPath;

	}

	/**
	 * 驼峰格式字符串转换为下划线格式字符串
	 *
	 * @param param
	 * @return
	 */
	private static String camel2Underline(String param) {
		if (param == null || "".equals(param.trim())) {
			return "";
		}
		int len = param.length();
		StringBuilder sb = new StringBuilder(len);
		for (int i = 0; i < len; i++) {
			char c = param.charAt(i);
			if (Character.isUpperCase(c)) {
				sb.append(UNDERLINE);
				sb.append(Character.toLowerCase(c));
			} else {
				sb.append(c);
			}
		}
		return sb.toString();
	}

	/**
	 * 根据类获取表名
	 *
	 * @param superClass
	 * @return
	 */
	private static String getTableNameByClass(Class<?> superClass) {
		String tableName;
		Table annotation = superClass.getAnnotation(Table.class);
		String prefix = (annotation == null || annotation.prefix().trim().isEmpty()) ? "" : annotation.prefix().trim() + "_";
		if (annotation != null && !annotation.value().isEmpty()) {
			tableName = annotation.value().trim();
		} else {
			tableName = lowerFirstCase(superClass.getSimpleName());
			if (MybatisConfig.getIsCamel()) {
				tableName = camel2Underline(tableName);
			}

		}
		return (prefix + tableName).intern();
	}

	/**
	 * 首字母小写
	 *
	 * @param name 需要首字母小写的名称
	 * @return
	 */
	private static String lowerFirstCase(String name) {
		if (name.isEmpty()) {
			return EMPTY;
		}
		char[] chars = name.toCharArray();
		chars[0] += 32;
		return String.valueOf(chars);
	}


	/**
	 * 获取表的注释
	 *
	 * @param tableName 表名字
	 * @param db        数据库
	 * @param conn      连接池
	 * @return
	 */
	private static String getTableCommit(String tableName, String db, Connection conn) {
		String getTableCommitSql = "SELECT table_comment FROM information_schema.TABLES WHERE table_schema = '%s' and table_name ='%s'";

		String commit = EMPTY;

		PreparedStatement ps = null;
		ResultSet rs = null;
		try {
			ps = conn.prepareStatement(String.format(getTableCommitSql, db, tableName));

			rs = ps.executeQuery();
			while (rs.next()) {
				commit = rs.getString(1);

			}
		} catch (SQLException ex) {


		} finally {
			if (rs != null) {

				try {
					rs.close();
				} catch (SQLException e) {
				}
			}

			if (ps != null) {
				try {
					ps.close();
				} catch (SQLException e) {
				}
			}


		}
		return commit;
	}

	/**
	 * 获得属性的注视
	 *
	 * @param tableName 表名字
	 * @param db        数据库
	 * @param conn      连接池
	 * @return
	 */
	private static Map<String, String> getColumnCommit(String tableName, String db, Connection conn) {
		String showColumnDes =
			"SELECT  column_name,column_comment FROM information_schema.columns WHERE table_schema ='%s' AND  table_name = '%s'";


		PreparedStatement ps = null;
		ResultSet rs = null;
		Map<String, String> map = new HashMap<>();
		try {
			ps = conn.prepareStatement(String.format(showColumnDes, db, tableName));

			rs = ps.executeQuery();
			while (rs.next()) {
				String fileName = rs.getString(1);
				String commit = rs.getString(2);

				map.put(fileName, commit == null ? EMPTY : commit);
			}
		} catch (SQLException ex) {


		} finally {
			if (rs != null) {

				try {
					rs.close();
				} catch (SQLException e) {
				}
			}

			if (ps != null) {
				try {
					ps.close();
				} catch (SQLException e) {
				}
			}


		}


		return map;

	}

	/**
	 * java和sql对应的关系
	 */
	private static class SqlRelationJavaUtil {
		private static Map<String, String> JAVA_TYPE_MAP = new HashMap<>();
		private static Map<String, String> RELATION_MAP = new HashMap<>();
		private static String FILE_NAME = "properties/sql2java.properties";

		static {

			putDefault();
			putFile();

		}

		private static void putFile() {
			InputStream resourceAsStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(FILE_NAME);
			if (resourceAsStream == null) {
				return;
			}
			Properties properties = new Properties();
			try {
				properties.load(resourceAsStream);
			} catch (IOException e) {

			}
			for (Map.Entry<Object, Object> entry : properties.entrySet()) {
				String javaType = entry.getValue().toString();
				String sqlType = entry.getKey().toString();
				int index = javaType.lastIndexOf(".");
				if (index < 0) {
					index = 0;
				} else {
					index += 1;
				}
				String javaSimpleType = javaType.substring(index);
				RELATION_MAP.put(sqlType, javaSimpleType);
				if (!javaType.contains(".") || javaType.startsWith("java.lang")) {
					JAVA_TYPE_MAP.put(sqlType, EMPTY);

				} else {
					JAVA_TYPE_MAP.put(javaSimpleType, javaType);
				}
			}
		}

		private static void putDefault() {

			RELATION_MAP.put("tinyint", "Integer");
			JAVA_TYPE_MAP.put("Integer", EMPTY);

			RELATION_MAP.put("smallint", "Integer");
			JAVA_TYPE_MAP.put("Integer", EMPTY);

			RELATION_MAP.put("ingeger", "Integer");
			JAVA_TYPE_MAP.put("Integer", EMPTY);


			RELATION_MAP.put("int", "Integer");
			JAVA_TYPE_MAP.put("Integer", EMPTY);

			RELATION_MAP.put("bigint", "Long");
			JAVA_TYPE_MAP.put("Long", EMPTY);

			RELATION_MAP.put("float", "Float");
			JAVA_TYPE_MAP.put("Float", EMPTY);


			RELATION_MAP.put("double", "Double");
			JAVA_TYPE_MAP.put("Double", EMPTY);


			RELATION_MAP.put("numeric", "BigDecimal");
			JAVA_TYPE_MAP.put("BigDecimal", "java.math.BigDecimal");

			RELATION_MAP.put("decimal", "BigDecimal");
			JAVA_TYPE_MAP.put("BigDecimal", "java.math.BigDecimal");


			RELATION_MAP.put("bit", "Boolean");
			JAVA_TYPE_MAP.put("Boolean", EMPTY);


			RELATION_MAP.put("varchar", "String");
			JAVA_TYPE_MAP.put("String", EMPTY);

			RELATION_MAP.put("char", "String");
			JAVA_TYPE_MAP.put("String", EMPTY);


			RELATION_MAP.put("date", "java.sql.Date");
			JAVA_TYPE_MAP.put("java.sql.Date", "java.sql.Date");


			RELATION_MAP.put("time", "Time");
			JAVA_TYPE_MAP.put("Time", "java.sql.Time");

			RELATION_MAP.put("datetime", "Date");
			JAVA_TYPE_MAP.put("Date", "java.util.Date");


			RELATION_MAP.put("timestamp", "Timestamp");
			JAVA_TYPE_MAP.put("Timestamp", "java.sql.Timestamp");


			RELATION_MAP.put("varbinary", "byte[]");
			JAVA_TYPE_MAP.put("byte[]", EMPTY);


			RELATION_MAP.put("blob", "Blob");
			JAVA_TYPE_MAP.put("Blob", "java.sql.Blob");

		}
	}


}
